package com.higgins.server.projectmanagement.datamodel;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.higgins.client.projectmanagement.HistoryItemDTO;
import com.higgins.client.projectmanagement.ProjectItemDTO;
import com.higgins.client.projectmanagement.ProjectService;
import com.higgins.server.DateUtility;
import com.higgins.server.knowledge.KnowledgeServiceImpl;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class ProjectServiceImpl extends RemoteServiceServlet implements ProjectService {
	private static final Logger log = Logger.getLogger(ProjectServiceImpl.class.getName());
	private EntityManagerFactory emfInstance = Persistence
			.createEntityManagerFactory("com.higgins.server.jpa");

	public ProjectServiceImpl()	{		 
	}
	public void doGet(HttpServletRequest req, HttpServletResponse resp)	throws IOException	{
		log.info("Ping Received!");
	}

	@Override
	public List<ProjectItemDTO> retrieveProjectItems(String level, Long parentId)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		List<ProjectItemDTO> dtoList = new ArrayList<ProjectItemDTO>();
		if (parentId == null)	{
			Query query = null;
			if (level==null)	{
				query = entityManager.createQuery( "from ProjectItem s where s.parentId IS NULL order by priority asc");
			} else {
				query = entityManager.createQuery( "from ProjectItem s where s.parentId IS NULL and s.task = :levelParam order by priority asc");
				query.setParameter("levelParam", level);
			}
			List<ProjectItem> results = query.getResultList();
			System.out.println("Size of list = " + results.size());
			for (ProjectItem result:results)	{
				if (isPermanent(result) || isNotComplete(result))	{
					ProjectItemDTO dto = result.toDTO();
					dtoList.add(dto);
				}
			}

		
		} else {
			Query query = entityManager.createQuery( "from ProjectItem s where s.parentId = :arg1 order by priority asc");
			query.setParameter("arg1", parentId);
			List<ProjectItem> results = query.getResultList();
			System.out.println("Size of list = " + results.size());
			for (ProjectItem result:results)	{
				if (isPermanent(result) || isNotComplete(result))	{
					ProjectItemDTO dto = result.toDTO();
					dtoList.add(dto);
				}
			}
		}
		entityManager.close();	
		return dtoList;
	}

	@Override
	public List<ProjectItemDTO> retrievePreviousProjectItems(Long parentId)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		List<ProjectItemDTO> dtoList = new ArrayList<ProjectItemDTO>();
		if (parentId == null)	{
			return null;
		} else {
			ProjectItem parentItem = getProjectItemById(entityManager, parentId);
			Query query = entityManager.createQuery( "from ProjectItem s where s.parentId = :arg1 order by priority asc");
			query.setParameter("arg1", parentItem.getParentId());
			List<ProjectItem> results = query.getResultList();
			System.out.println("Size of list = " + results.size());
			for (ProjectItem result:results)	{
				if (isPermanent(result) || isNotComplete(result))	{
					ProjectItemDTO dto = result.toDTO();
					dtoList.add(dto);
				}
			}
		}
		entityManager.close();	
		return dtoList;
	}
	
	private Boolean isPermanent(ProjectItem projectItem)	{
		if (projectItem.getPermanent()!=null)	{
			return projectItem.getPermanent();
		} else 	{
			return false; 
		}
	}
	private Boolean isNotComplete(ProjectItem projectItem)	{
		if ((projectItem.getComplete()==null) || (projectItem.getComplete() < 100.0))	{
			return true;
		}	else {
			return false;
		}
	}

	private List<ProjectItem> retrievePersistentProjectItems(Long parentId)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		List<ProjectItemDTO> dtoList = new ArrayList<ProjectItemDTO>();
	
		Query query = entityManager.createQuery( "from ProjectItem s where s.parentId = :arg1 order by priority asc");
		query.setParameter("arg1", parentId);
		List<ProjectItem> children = query.getResultList();
		entityManager.close();		
		return children;
	}
	private ProjectItem getProjectItemById(EntityManager entityManager, Long id)	{
		List<ProjectItemDTO> dtoList = new ArrayList<ProjectItemDTO>();
	
		Query query = entityManager.createQuery( "from ProjectItem s where s.id = :arg1 order by priority asc");
		query.setParameter("arg1", id);
		List<ProjectItem> children = query.getResultList();
		if (children.size() > 0)
			return children.get(0);
		else 
			return null;
	}
	public List<Effort> getEffortByParentId(EntityManager entityManager, Long id, boolean nullCheck)	{
		if (id !=null)	{
			Query query = entityManager.createQuery( "from Effort s where s.parentId = :arg1" + (nullCheck ? " and s.stop IS NULL ":""));
			query.setParameter("arg1", id);
			List<Effort> children = query.getResultList();
			return children;
		} else {
			Query query = entityManager.createQuery( "from Effort s " + (nullCheck ? "where s.stop IS NULL" : ""));
			List<Effort> children = query.getResultList();
			return children;
		}
	}
	public List<Effort> getEffortByParentId(EntityManager entityManager, Long id)	{
		return getEffortByParentId(entityManager, id, true);
	}
	
	@Override
	public List<Long> retrieveParentIds(Long childId)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		Long parentId = new Long(0);
		List<Long> parentIds = new ArrayList<Long>();
		if (childId!=null)	{
			parentId = childId;
			while (parentId!=null)	{
				ProjectItem item = null;
				try	{
					item = getProjectItemById(entityManager, parentId);
					parentIds.add(parentId);
					parentId = item.getParentId();
				} catch (Exception e)	{
					parentId = null;
				}
			}
		}
		entityManager.close();
		return parentIds;
	}
	

	@Override
	public Long insetProjectItem(ProjectItemDTO item)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		ProjectItem parent = new ProjectItem();
		parent.update(item);
		parent.setStartTimestamp(new Date());
		entityManager.getTransaction().begin();
		entityManager.persist(parent);
		entityManager.getTransaction().commit();
		entityManager.close();
		return parent.getId();
	}

	@Override
	public Long updateProjectItemTask(Long id, String task, Long parentId)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setTask(task);
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return item.getId();
		} else {
			entityManager.getTransaction().begin();
			ProjectItem item = new ProjectItem();
			item.setParentId(parentId);
			item.setTask(task);
			item.getId();
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return item.getId();
		}
	}

	@Override
	public Long updateProjectItemForecast(Long id, Double forecast,
			Long parentId) throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setForecast(forecast);
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			updateParentForecasts(id);
			updateParentCompletes(id);
			return item.getId();
		} else {
			entityManager.getTransaction().begin();
			ProjectItem item = new ProjectItem();
			item.setParentId(parentId);
			item.setForecast(forecast);
			item.getId();
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			updateParentForecasts(item.getId());
			updateParentCompletes(item.getId());
			return item.getId();
		}
	}

	private void updateParentForecasts(Long id)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id !=null)	{
			ProjectItem parentItem = null;
			ProjectItem requestedItem = getProjectItemById(entityManager, id);
			do	{
				parentItem = getProjectItemById(entityManager, requestedItem.getParentId());
				Double total = 0.0;
				for (ProjectItem childItem:retrievePersistentProjectItems(parentItem.getId()))
					total = total + childItem.getForecast();
				parentItem.setForecast(total);
				entityManager.getTransaction().begin();
				entityManager.persist(parentItem);
				entityManager.getTransaction().commit();
				requestedItem = parentItem;
			} while (parentItem.getParentId()!=null);
		}
		entityManager.close();
	}
	
	private void updateParentCompletes(Long id)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id !=null)	{
			ProjectItem requestedItem = getProjectItemById(entityManager, id);
			ProjectItem parentItem = null;
			do	{
				parentItem = getProjectItemById(entityManager, requestedItem.getParentId());
				Double totalForecast = 0.0;
				Double totalComplete = 0.0;
				for (ProjectItem childItem:retrievePersistentProjectItems(parentItem.getId()))	{
					totalForecast = totalForecast + (childItem.getForecast()!=null?childItem.getForecast():0.0);
					totalComplete = totalComplete + ((childItem.getForecast()!=null?childItem.getForecast():0.0) * ((childItem.getComplete()!=null?childItem.getComplete():0.0)/100.0));
				}
				parentItem.setComplete(100.0*totalComplete/totalForecast);
				entityManager.getTransaction().begin();
				entityManager.persist(parentItem);
				entityManager.getTransaction().commit();
				requestedItem = parentItem;
			} while (parentItem.getParentId()!=null);
		}
	}

	@Override
	public Long updateProjectItemComplete(Long id, Double complete,
			Long parentId) throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setComplete(complete);
			if (complete > 99.0)	{
				item.setEndTimestamp(new Date());
			}
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			updateParentCompletes(id);
			return item.getId();
		} else {
			entityManager.getTransaction().begin();
			ProjectItem item = new ProjectItem();
			item.setParentId(parentId);
			item.setComplete(complete);
			item.getId();
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			updateParentCompletes(item.getId());
			return item.getId();
		}
	}

	@Override
	public List<Long> updateProjectItemPriority(Long idOne,
			Integer priorityOne, Long idTwo, Integer priorityTwo, Long parentId)
			throws IllegalArgumentException {
		Long returnOne = updateProjectItemPriority(idOne, priorityOne);
		Long returnTwo = updateProjectItemPriority(idTwo, priorityTwo);
		List<Long> list = new ArrayList<Long>();
		if (returnOne!=null)
			list.add(returnOne);
		if (returnTwo!=null)
			list.add(returnTwo);
		return list;
	}
	private Long updateProjectItemPriority(Long id, Integer priority)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setPriority(priority);
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return item.getId();
		} 	
		entityManager.close();
		return null;
	}
	
	@Override
	public Boolean deleteProjectItem(Long id) throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			entityManager.remove(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return true;
		}
		entityManager.close();
		return false;
	}

	private List<Effort> retrieveAllEfforts(EntityManager entityManager, Date startDate, Date endDate)	{
		Query query = entityManager.createQuery( "from Effort s where start > :startDateParam and start < :endDateParam order by start asc");
		query.setParameter("startDateParam", startDate);
		query.setParameter("endDateParam", endDate);
		List<Effort> children = query.getResultList();
		return children;
	}
	
	@Override
	public Boolean startTimer(Long id) throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();		
		stopTimer(null);
		if (id!=null)	{
			entityManager.getTransaction().begin();
			Effort effort = new Effort();
			effort.setParentId(id);
			effort.setStart(new Date());
			entityManager.persist(effort);
			entityManager.getTransaction().commit();
		}
		entityManager.close();
		return true;
	}

	@Override
	public Boolean stopTimer(Long id) throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();	
		List<Effort> efforts = getEffortByParentId(entityManager, id);
		for (Effort effort:efforts)	{
			entityManager.getTransaction().begin();
			effort.setStop(new Date());
			entityManager.persist(effort);
			entityManager.getTransaction().commit();
		}
		entityManager.close();
		updateActual(id);
		updateParentActuals(id);		
		return true;
	}

	@Override
	public Long isActive(Long id) throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();		
		if (id!=null)	{
			List<Effort> efforts = getEffortByParentId(entityManager, id);
			if (efforts.size()>0)
				return id;
		}
		entityManager.close();
		return null;
	}

	@Override
	public List<HistoryItemDTO> retrieveHistory(String dateStr,
			Boolean showAllEvents, Boolean showDistinctList)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();		
		String pattern = "dd/MM/yyyy";
		SimpleDateFormat format = new SimpleDateFormat(pattern);
		Date date = new Date();
		try	{
			date = format.parse(dateStr);
		} catch (Exception e)	{
			
		}
		
		List<HistoryItemDTO> history = new ArrayList<HistoryItemDTO>();
		
		List<Effort> efforts = retrieveAllEfforts(entityManager, DateUtility.getStartOfDay(date), DateUtility.getEndOfDay(date));
		
		for (Effort effort:efforts)	{
			Long projectItemId = effort.getParentId();
			String historyText = "";
			
			List<Long> parentIds = retrieveParentIds(projectItemId);
			for (Long parendId:parentIds) {
				ProjectItem projectItem = getProjectItemById(entityManager, parendId);
				if (projectItem!=null)	{
					historyText = projectItem.getTask() + (historyText.equals("")?"":" -> ") + historyText; 
				}
			}
			
			String effortStart = effort.getStart()!=null?effort.getStart().toString().substring(0, (effort.getStart().toString().length()-9)):"";
			String effortStop = effort.getStop()!=null?effort.getStop().toString().substring(10, (effort.getStop().toString().length()-9)):"";
			
			if (!showDistinctList)
				historyText = effortStart + " to " + effortStop + ": " + historyText;

			HistoryItemDTO historyItemDTO = new HistoryItemDTO();
			historyItemDTO.setDescription(historyText);
			historyItemDTO.setTimestamp(effort.getStart());
			if (showDistinctList)	{
				if (!historyArrayContains(history, historyText))	{
					history.add(historyItemDTO);
				}
			} else {
				history.add(historyItemDTO);
			}
		}	
		if (showDistinctList)	{
			Collections.sort(history, new alphaComparator());
		} else {
			Collections.sort(history);
		}
		entityManager.close();
		return history;
	}
	private boolean historyArrayContains(List<HistoryItemDTO> list, String historyText)	{
		for (HistoryItemDTO historyItem:list)	{
			if (historyItem.getDescription().equals(historyText))
				return true;
		}
		return false;
	}
	public class alphaComparator implements Comparator<HistoryItemDTO> {
	    final public int compare(HistoryItemDTO object1, HistoryItemDTO object2) {
	        return object1.getDescription().compareTo(object2.getDescription());
	    }
	}
	@Override
	public List<String> retrieveImmediate(String level) throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();		

		Query query = null;
		query = entityManager.createQuery( "from ProjectItem s where s.immediate = :arg1 order by priority asc");		
		query.setParameter("arg1", true);
		List<ProjectItem> results = query.getResultList();
		List<String> immediateItems = new ArrayList<String>();
		for (ProjectItem projectItem:results)	{
			if (isNotComplete(projectItem) && isSameLevel(entityManager, level, projectItem))	{
				immediateItems.add(projectItem.getTask());
			}
		}
		entityManager.close();
		return immediateItems;
	}

	private boolean isSameLevel(EntityManager entityManager, String level, ProjectItem projectItem)	{
		if (level==null)	{
			return true;
		}
		List<Long> parentIds = retrieveParentIds(projectItem.getId());
		int size = parentIds.size();
		if (size>0)	{
			Long parentId = parentIds.get(size-1);
			ProjectItem parentItem = getProjectItemById(entityManager, parentId);
			if (level.equals(parentItem.getTask()))	{
				return true;
			}
		} 
		return false;
	}
	
	@Override
	public List<String> retrieveFollowUp() throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();		
		Query query = entityManager.createQuery( "from ProjectItem s where s.complete < :arg1 and s.followUpDate < :arg2 and s.followUp = :arg3 order by priority asc");
		query.setParameter("arg1", 100.0);
		query.setParameter("arg2", new Date());
		query.setParameter("arg3", true);
		List<ProjectItem> results = query.getResultList();
		List<String> followUpItems = new ArrayList<String>();
		for (ProjectItem projectItem:results)	{
//			if (isNotComplete(projectItem) && isFollowUpDatePast(projectItem))	{
				followUpItems.add(projectItem.getTask());
//			}
		}
		return followUpItems;
	}

	@Override
	public String retrieveTodaysSimpleFormatDate()
			throws IllegalArgumentException {
		String pattern = "dd/MM/yyyy";
		SimpleDateFormat format = new SimpleDateFormat(pattern);
		return format.format(new Date());	
	}

	@Override
	public Boolean setPermanent(Long id) throws IllegalArgumentException {
		return setPermanent(id, true);
	}

	@Override
	public Boolean setNotPermanent(Long id) throws IllegalArgumentException {
		return setPermanent(id, false);
	}
	private Boolean setPermanent(Long id, boolean permanent)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setPermanent(permanent);
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return true;
		} 
		entityManager.close();
		return false;
	}

	@Override
	public Boolean setImmediate(Long id) throws IllegalArgumentException {
		return setImmediate(id, true);
	}

	@Override
	public Boolean setNotImmediate(Long id) throws IllegalArgumentException {
		return setImmediate(id, false);
	}

	private Boolean setImmediate(Long id, boolean immediate)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setImmediate(immediate);
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return true;
		} 
		entityManager.close();
		return false;
	}
	
	@Override
	public Boolean setFollowUp(Long id) throws IllegalArgumentException {
		return setFollowUp(id, true);
	}

	@Override
	public Boolean setNotFollowUp(Long id) throws IllegalArgumentException {
		return setFollowUp(id, false);
	}

	private Boolean setFollowUp(Long id, boolean followUp)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{	
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setFollowUp(followUp);
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return true;
		} 
		entityManager.close();
		return false;
	}
	@Override
	public Long updateFollowUpDate(Long id, String followUpDateString)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		Date followUpDate = DateUtility.convertStringToDate(followUpDateString);
		if (id != null)	{
			ProjectItem item = getProjectItemById(entityManager, id);
			entityManager.getTransaction().begin();
			item.setFollowUpDate(followUpDate);
			item.setFollowUpDateString(followUpDateString);
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return id;
		}
		entityManager.close();
		return null;
	}

	@Override
	public Long updateStartTimestamp(Long id, Date startTimestamp)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setStartTimestamp(new Date());
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return id;
		}
		entityManager.close();
		return null;
	}

	@Override
	public Long updateEndTimestamp(Long id, Date endTimestamp)
			throws IllegalArgumentException {
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id != null)	{
			entityManager.getTransaction().begin();
			ProjectItem item = getProjectItemById(entityManager, id);
			item.setEndTimestamp(new Date());
			entityManager.persist(item);
			entityManager.getTransaction().commit();
			entityManager.close();
			return id;
		}
		entityManager.close();
		return null;
	}

	private void updateActual(Long id)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id!=null)	{
			ProjectItem item = getProjectItemById(entityManager, id);
			List<Effort> efforts = getEffortByParentId(entityManager, id, false);
			Double totalEffort = 0.0;
			for (Effort effort:efforts)
				totalEffort = totalEffort + effort.getDuration();
			item.setActual(totalEffort);
			entityManager.getTransaction().begin();
			entityManager.persist(item);
			entityManager.getTransaction().commit();
		}
		entityManager.close();
	}
	
	private void updateParentActuals(Long id)	{
		EntityManager entityManager = emfInstance.createEntityManager();
		if (id !=null)	{
			ProjectItem requestedItem = getProjectItemById(entityManager, id);
			ProjectItem parentItem = null;
			do	{
				parentItem = getProjectItemById(entityManager, requestedItem.getParentId());
				Double total = 0.0;
				for (ProjectItem childItem:retrievePersistentProjectItems(parentItem.getId()))
					total = total + childItem.getActual();
				parentItem.setActual(total);
				entityManager.getTransaction().begin();
				entityManager.persist(parentItem);
				entityManager.getTransaction().commit();
				requestedItem = parentItem;
			} while (parentItem!=null && parentItem.getParentId()!=null);
		}
		entityManager.close();
	}
	
	@Override
	public Boolean checkAccess() throws IllegalArgumentException {
		// TODO Auto-generated method stub
		return true;
	}

}
